23.06.02
오늘 한 일
- 알고리즘 문제 풀이
- 카공실록 개발
-
MSW 세팅 - 카공 기록 바텀시트 UI 구현
-
- 부스트캠프 자소서 작성
카공실록 개발
MSW 세팅 문제
MSW를 사용하려고 했다..
근데 어제 TIL 쓰고 좀 찾아보다가 이 discussion을 발견했다.
Next13의 App directory를 지원하지 않는다는 내용이다. 혹시라도 우회해서 할 수 있는 방법이 없나 열심히 찾아보았다.
다른 연합 동아리에서 진행하고 있는 프로젝트도 찾아보니 Next.js 13을 사용하면서 MSW를 사용하고 있는 프로젝트가 있었다. 그 팀들은 아직 개발 초기라 기본 세팅만 되있는 상태였다.
하지만 자세히보니 msw/node
만을 사용하고 있었고, msw/browser
는 사용하지 않고 있었다. msw를 mock 테스트 용으로만 사용하고 있었다.
내가 원했던건 msw/browser
를 사용해서 api를 개발 환경에서 모킹해서 사용하는 것이었다. 그래서 직접 msw/browser
를 사용해서 세팅을 해보려고 했다.
어찌어찌 동작하게 할 순 있었지만, 여러 에러가 많이 터졌다. 어쩔 수 없이 msw를 사용하지 않기로 결정했다.
대신 json 형태로 더미데이터를 저장해두어 get 요청으로 사용하는 방식으로 개발을 진행하기로 했다.
fetching tool
난 항상 axios로 HTTP 요청을 보내왔다. 그런데 Next.js 13에서는 내장 함수인 fetch
를 사용하도록 권장하고 있다.
그리고 이걸 사용해야 SSR, SSG 이런 렌더링 방식을 사용할 수 있다고 한다.
// app/page.tsx
export default async function Page() {
// This request should be cached until manually invalidated.
// Similar to `getStaticProps`.
// `force-cache` is the default and can be omitted.
const staticData = await fetch(`https://...`, { cache: 'force-cache' });
// This request should be refetched on every request.
// Similar to `getServerSideProps`.
const dynamicData = await fetch(`https://...`, { cache: 'no-store' });
// This request should be cached with a lifetime of 10 seconds.
// Similar to `getStaticProps` with the `revalidate` option.
const revalidatedData = await fetch(`https://...`, {
next: { revalidate: 10 },
});
return <div>...</div>;
}
axios는 지원하지 않고 있기 때문에, 다른 방안을 찾아보았다.
이것도 역시 다른 프로젝트들을 둘러봤는데 ky
라이브러리를 사용하고 있었다.
ky
는 axios와 비슷한 라이브러리인데, fetch
를 기반으로 만들어진 라이브러리이다. 그래서 Next.js 13에서도 사용할 수 있는 것 같다.
axios
의 방식
import axios from 'axios';
import type { PlaceType } from '@/types/place';
export const getPlace = async (id: string) => {
const { data } = await axios.get<PlaceType>('/db/place.json');
return data;
};
ky
의 방식
import ky from 'ky';
import type { PlaceType } from '@/types/place';
export const getPlace = async (id: string) => {
const data = await ky.get('/db/place.json');
return data.json<PlaceType>();
};
@toss/ky 를 사용할까?
ky는 browser 환경에서 사용하기 위해 만들어진 라이브러리이다. 그래서 node 환경에서는 ky-universal
을 사용해야 한다. 이걸 한 번에 해결해주는 라이브러리가 @toss/ky
이다.
@toss/ky의 설명
왜 필요한가요?
ky 라이브러리는 아래와 같은 문제를 가지고 있습니다.
ESM-only 라이브러리이기 때문에 CJS로 require() 하려고 하는 경우 오류가 발생합니다.
SSR 대응을 위해서는 ky-universal 라이브러리를 사용해야 합니다.
@toss/ky는 ky 를 쉽게 사용할 수 있도록 이를 개선합니다.
ESBuild로 한 차례 빌드함으로써 CJS-ESM 모두에서 사용할 수 있도록 하고,
서버에서도 ky-universal을 쓸지, ky 를 쓸지, 구분할 필요 없이 @toss/ky 만 쓰면 되도록 합니다.
그래서 @toss/ky
를 사용하기로 했는데, 한 가지 문제가 있었다.
@toss/ky에는 ky의 타입을 내장하고 있지 않았다. 혹시나 DefinetlyTyped에 ky의 타입이 있을까 찾아봤는데, 없었다.
그래서 그냥 ky
를 사용하기로 했다.
카공기록 바텀시트 UI 구현
여기서 가운데 보이는 스크롤 가능한 DatePicker는 아직 구현하지 않았다. 마땅한 라이브러리를 못 찾아서 그냥 내가 만들어야 할 것 같다.
이미지를 업로드하는 부분이 저번에 만들었던 리뷰등록 바텀시트에서도 존재했어서, ImageUpload
라는 컴포넌트로 분리해서 재사용하도록 했다.
import Image from 'next/image';
interface ImageUploadProps {
images: File[];
onUpload: (imageFiles: File[]) => void;
className?: string;
}
export default function ImageUpload({ images, onUpload, className }: ImageUploadProps) {
const handleUpload = () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.multiple = true;
input.onchange = (e) => {
const files = Array.from((e.target as HTMLInputElement).files!).filter((file) => {
for (const image of images) {
if (file.name === image.name && file.size === image.size) return false;
}
return true;
});
if (images.length + files.length > 5) {
alert('이미지는 최대 5개까지 업로드할 수 있습니다.');
return;
}
onUpload([...images, ...files]);
};
input.click();
};
return (
<div className={`flex w-[calc(100%+1.5rem)] gap-2 pr-6 ${className ?? ''}`}>
<div
className='flex h-[72px] w-[72px] shrink-0 cursor-pointer flex-col items-center justify-center bg-background'
onClick={handleUpload}
>
<Image src='/assets/icons/32/Camera.svg' alt='camera' width={32} height={32} />
<p className='text-caption'>
{images.length}
<span className='text-bk40'>/5</span>
</p>
</div>
<div className='flex gap-2 overflow-hidden overflow-x-scroll'>
{images.map((image, index) => (
<div key={index} className='relative h-[72px] w-[72px] flex-shrink-0'>
<Image src={URL.createObjectURL(image)} alt='image' className='object-cover' fill />
<Image
src='/assets/icons/16/Delete.svg'
alt='close'
className='absolute right-1 top-1 cursor-pointer'
width={16}
height={16}
onClick={() => {
onUpload(images.filter((_, i) => i !== index));
}}
/>
</div>
))}
</div>
</div>
);
}
이미지를 업로드하는 부분은 대충 이런 식으로 작성하면 된다.
import { useState } from 'react';
import ImageUpload from '@/components/ImageUpload';
export default function ReviewBottomSheet() {
const [images, setImages] = useState<File[]>([]);
return <ImageUpload images={images} onUpload={(imageFiles) => setImages(imageFiles)} />;
}
이미지가 최대 5개까지 들어가야 해서 제한을 걸어두었다. 그리고 이미지의 우측 상단에 X 버튼을 누르면 filter 메서드를 통해 해당 index의 이미지를 제외한 나머지 이미지들을 반환하도록 했다.
내일 할 일
- 알고리즘 문제 풀이
- 카공실록 갤러리 페이지 구현
- 리뷰 상세 페이지 구현
- 소프트웨어공학 시험 공부